﻿/*
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.
*/
package com.jorritdevries.console
{
	import com.jorritdevries.console.elements.CloseButton;
	import com.jorritdevries.console.elements.Cursor;
	import com.jorritdevries.console.elements.MaximizeButton;
	import com.jorritdevries.console.elements.MinimizeButton;
	import com.jorritdevries.console.elements.ResizeArea;
	import com.jorritdevries.console.elements.Titlebar;
	import com.jorritdevries.console.elements.MainWindow;
	import fl.controls.Button;
	import fl.controls.ScrollPolicy;
	import fl.controls.TextArea;
	import fl.controls.TextInput;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Shape;
	import flash.events.FocusEvent;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.utils.getDefinitionByName;
	
	public class UIController
	{
		private var mConsole:UIConsole;
		private var mMainWindow:MainWindow;
		private var mResizeArea:ResizeArea;
		private var mCorner:Bitmap;
		
		private var mTitlebar:Titlebar;
		private var mTitleField:TextField;
		private var mCloseButton:CloseButton;
		private var mMinimizeButton:MinimizeButton;
		private var mMaximizeButton:MaximizeButton;
		private var mMinimized:Boolean;
		private var mMaximizing:Boolean;
		
		private var mFPSField:TextField;
		private var mAvgFPSField:TextField;
		private var mMillisecField:TextField;
		private var mMemoryUsageField:TextField;
		private var mFPSCalculator:FPSCalculator;
		
		private var mLine:Shape;
		private var mLinePosY:uint;
		
		private var mOutputArea:TextArea;
		private var mInputField:TextInput;
		private var mEnterButton:Button;
		
		private var mWidht:Number;
		private var mHeight:Number;
		private var mLastHeight:Number;
		
		public static const MIN_WIDTH:uint  = 300;
		public static const MIN_HEIGHT:uint = 160;
		public static const BORDER_SIZE:uint = 3;
		public static const CORNER_SIZE:int = 3;
		public static const RESIZE_BORDER_SIZE:uint = 5;
		public static const RESIZE_CORNER_SIZE:int = 16;
		
		private static const BUTTON_SPACING_SIZE:uint = 2;
		
		public function UIController(container:UIConsole)
		{
			mConsole = container;
			
			mMainWindow = new MainWindow();
			mConsole.addChild(mMainWindow);
			
			var ResizeCorner:Class = getDefinitionByName("com.jorritdevries.console.skin.ResizeCorner") as Class;
			mCorner = new Bitmap(new ResizeCorner(10, 10) as BitmapData);
			mConsole.addChild(mCorner);
			
			mTitlebar = new Titlebar();
			mTitlebar.addEventListener(MouseEvent.MOUSE_DOWN,
				function(e:MouseEvent):void { mConsole.startDrag(); });
			mTitlebar.addEventListener(MouseEvent.MOUSE_UP,
				function(e:MouseEvent):void { mConsole.stopDrag(); });
			mConsole.addChild(mTitlebar);
			
			var format:TextFormat = new TextFormat();
			format.font = "Verdana";
			format.size = 10;
			format.color = 0x000000;
			
			mTitleField = new TextField();
			mTitleField.selectable = false;
			mTitleField.defaultTextFormat = format;
			mTitleField.text = "Console";
			mTitleField.x = CORNER_SIZE;
			mTitleField.height = mTitlebar.height;
			mConsole.addChild(mTitleField);
			
			mCloseButton = new CloseButton();
			mCloseButton.y = BUTTON_SPACING_SIZE;
			mCloseButton.addEventListener(MouseEvent.CLICK, mouseClickHandler);
			mConsole.addChild(mCloseButton);
			
			mMinimizeButton = new MinimizeButton();
			mMinimizeButton.y = BUTTON_SPACING_SIZE;
			mMinimizeButton.addEventListener(MouseEvent.CLICK, mouseClickHandler);
			mConsole.addChild(mMinimizeButton);
			
			mMaximizeButton = new MaximizeButton();
			mMaximizeButton.y = BUTTON_SPACING_SIZE;
			mMaximizeButton.visible = false;
			mMaximizeButton.addEventListener(MouseEvent.CLICK, mouseClickHandler);
			mConsole.addChild(mMaximizeButton);
			
			mMinimized = mMaximizing = false;
			
			// Statistics fields			
			mFPSField = new TextField();
			mFPSField.multiline = false;
			mFPSField.selectable = false;
			mFPSField.defaultTextFormat = format;
			mFPSField.x = BORDER_SIZE;
			mFPSField.y = 15 + BORDER_SIZE;
			mFPSField.htmlText = "<b>FPS:</b> N/A";
			mConsole.addChild(mFPSField);
			
			mAvgFPSField = new TextField();
			mAvgFPSField.multiline = false;
			mAvgFPSField.selectable = false;
			mAvgFPSField.defaultTextFormat = format;
			mAvgFPSField.x = 85 + BORDER_SIZE;
			mAvgFPSField.y = 15 + BORDER_SIZE;
			mAvgFPSField.text = "<b>avgFPS:</b> N/A";
			mConsole.addChild(mAvgFPSField);
			
			mMillisecField =  new TextField();
			mMillisecField.multiline = false;
			mMillisecField.selectable = false;
			mMillisecField.defaultTextFormat = format;
			mMillisecField.x = 200 + BORDER_SIZE;
			mMillisecField.y = 15 + BORDER_SIZE;
			mMillisecField.htmlText = "<b>ms:</b> N/A";
			mConsole.addChild(mMillisecField);
			
			mMemoryUsageField = new TextField();
			mMemoryUsageField.multiline = false;
			mMemoryUsageField.selectable = false;
			mMemoryUsageField.defaultTextFormat = format;
			mMemoryUsageField.x = BORDER_SIZE;
			mMemoryUsageField.y = 15 + (2 * BORDER_SIZE) + mFPSField.textHeight;
			mMemoryUsageField.htmlText = "<b>RAM:</b> N/A";
			mConsole.addChild(mMemoryUsageField);
			
			mFPSCalculator = FPSCalculator.getInstance();
			mFPSCalculator.initialise();
			
			// Simple line to split statistics and console functionality
			mLine = new Shape();
			mLine.graphics.lineStyle(1, 0xA4A4A4);
			mLinePosY = uint(15 + (4 * BORDER_SIZE) + mFPSField.textHeight + mMemoryUsageField.textHeight);
			mLine.graphics.moveTo(BORDER_SIZE, mLinePosY);
			mLine.graphics.lineTo(MIN_WIDTH - BORDER_SIZE, mLinePosY);
			mConsole.addChild(mLine);
			
			// Console stuff
			format = new TextFormat();
			format.font = "Courier New";
			format.size = 11;			
			
			mOutputArea = new TextArea();
			mOutputArea.editable = false;
			mOutputArea.wordWrap = true;
			mOutputArea.setStyle("textFormat", format);
			mOutputArea.setStyle("disabledTextFormat", format);
			mOutputArea.setSharedStyle("disabledTextFormat", format);
			mOutputArea.setSharedStyle("textFormat", format);
			mOutputArea.horizontalScrollPolicy = ScrollPolicy.OFF;
			mOutputArea.verticalScrollPolicy = ScrollPolicy.AUTO;
			mOutputArea.x = BORDER_SIZE;
			mOutputArea.y = mLinePosY + (2 * BORDER_SIZE);
			mConsole.addChild(mOutputArea);
			
			mInputField = new TextInput();
			mInputField.x = BORDER_SIZE;
			mInputField.setStyle("textFormat", format);
			mInputField.y = mOutputArea.y + mOutputArea.height + BORDER_SIZE;
			mInputField.htmlText += "Enter command...";
			mInputField.addEventListener(FocusEvent.FOCUS_IN, focusInHandler);
			mInputField.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
			mConsole.addChild(mInputField);
			
			mEnterButton = new Button();
			mEnterButton.label = "Execute";
			mEnterButton.width = 60;
			mEnterButton.x = mInputField.width - mEnterButton.width - BORDER_SIZE;
			mEnterButton.y = mInputField.y;
			mEnterButton.addEventListener(MouseEvent.CLICK, mouseClickHandler);
			mConsole.addChild(mEnterButton);
			
			mResizeArea = new ResizeArea();
			mResizeArea.addEventListener(ResizeEvent.RESIZE, resizeHandler);
			mConsole.addChild(mResizeArea);
			
			// Add cursor as last
			var cursor:Cursor = Cursor.getInstance();
			mConsole.addChild(cursor);
			
			// Add ad listener to message events
			Console.getInstance().addEventListener(ConsoleMessageEvent.MESSAGE, consoleMessageHandler);
			
			// Set size
			mWidht = MIN_WIDTH;
			mHeight = mLastHeight = MIN_HEIGHT;
		}
		
		internal function getWidth():Number
		{
			return mWidht;
		}
		
		internal function getHeight():Number
		{
			return mHeight;
		}
		
		internal function setTitle(title:String):void
		{
			mTitleField.text = title;
		}
		
		internal function getTitle():String
		{
			return mTitleField.text;
		}
		
		internal function updateStatistics():void
		{
			mFPSCalculator.update();
			
			if (!mMinimized)
			{
				mFPSField.htmlText = "<b>FPS:</b> " + mFPSCalculator.currentFPS.toFixed(2);
				mAvgFPSField.htmlText = "<b>avgFPS:</b> " + mFPSCalculator.averageFPS.toFixed(2); 
				mMillisecField.htmlText =  "<b>ms:</b> " + mFPSCalculator.timeSinceLastFrame;
				mMemoryUsageField.htmlText = "<b>RAM: </b>" + MemoryMonitor.memoryUsageInMegaBytes.toFixed(1) + "MB";
			}
		}
		
		internal function setSize(width:Number, height:Number):void
		{
			var w:Number = width;
			var h:Number = height;
			if (w < MIN_WIDTH)
			{
				w = MIN_WIDTH;
			}
			
			if (!mMinimized)
			{
				if (!mMaximizing)
				{
					mLastHeight = mHeight;
				}
				if (h < MIN_HEIGHT)
				{
					h = MIN_HEIGHT;
				}
			}
			else
			{
				h = mTitlebar.height + (2 * BORDER_SIZE);
			}
			
			mMaximizing = false;
			
			mWidht = w;
			mHeight = h;
			
			mMainWindow.setSize(w, h);
			mCorner.x = w - mCorner.width - CORNER_SIZE;
			mCorner.y = h - mCorner.height - CORNER_SIZE;
			mResizeArea.setSize(w, h);
			mTitlebar.setSize(w, h);
			
			mCloseButton.x = w - mCloseButton.width - BORDER_SIZE;
			mMinimizeButton.x = mMaximizeButton.x = mCloseButton.x - mMinimizeButton.width - BUTTON_SPACING_SIZE;
			
			mLine.graphics.clear();
			mLine.graphics.lineStyle(1, 0xA4A4A4);
			mLine.graphics.moveTo(BORDER_SIZE, mLinePosY);
			mLine.graphics.lineTo(w - BORDER_SIZE, mLinePosY);
			
			mOutputArea.setSize(w - (2 * BORDER_SIZE), h - 100);
			mInputField.setSize(w - mEnterButton.width - (3 * BORDER_SIZE), mInputField.height);
			mInputField.y = mOutputArea.y + mOutputArea.height + BORDER_SIZE;
			mEnterButton.y = mInputField.y;
			mEnterButton.x = w - mEnterButton.width - BORDER_SIZE;
			
			mConsole.dispatchEvent(new ResizeEvent(ResizeEvent.RESIZE, w, h));
		}
		
		internal function resizeHandler(e:ResizeEvent):void
		{
			setSize(e.width, e.height);
		}
		
		private function mouseClickHandler(e:MouseEvent):void
		{
			// Switches on a lot of types are possible in Actionscript :)
			switch (e.target)
			{
			case mEnterButton:
				issueCommand();
				break;
			case mCloseButton:
				mConsole.visible = false;
				break;
			case mMinimizeButton:
				mMinimizeButton.visible = false;
				mMaximizeButton.visible = mMinimized = true;
				mResizeArea.minimize();
				showControls(false);
				setSize(mWidht, mTitlebar.height + (2 * BORDER_SIZE));
				break;
			case mMaximizeButton:
				mMaximizeButton.visible = mMinimized = false;
				mMinimizeButton.visible = true;
				mMaximizing = true;
				setSize(mWidht, mLastHeight);
				mResizeArea.maximize();
				showControls(true);
				break;
			}
		}
		
		private function issueCommand():void
		{
			mConsole.sendCommand(mInputField.text);
			mInputField.text = "";
		}
		
		private function showControls(show:Boolean):void
		{
			mFPSField.visible = mAvgFPSField.visible = mMillisecField.visible =
				mMemoryUsageField.visible = mOutputArea.visible = mInputField.visible =
				mEnterButton.visible = mLine.visible = mCorner.visible = show;
		}
		
		private function focusInHandler(e:FocusEvent):void
		{
			// We know we listen to only one target
			mInputField.removeEventListener(FocusEvent.FOCUS_IN, focusInHandler);
			mInputField.text = "";
		}
		
		private function keyDownHandler(e:KeyboardEvent):void
		{
			switch (e.keyCode)
			{
			case 13: // Enter
				issueCommand();
				break;
			case 38: // Arrow down
				mInputField.text = Console.getInstance().getPreviousCommand();
				break;
			case 40: // Arrow down
				mInputField.text = Console.getInstance().getNextCommand();
				break;
			}
		}
		
		private function consoleMessageHandler(e:ConsoleMessageEvent):void
		{
			if (mOutputArea.text > "")
			{
				mOutputArea.text += "\n";
			}
			
			// Parse colour information from numerical to string
			var r:uint = (e.colour & 0xFF0000) >> 16;
			var g:uint = (e.colour & 0x00FF00) >> 8;
			var b:uint = (e.colour & 0x0000FF);
			var colour:String = addPrecedingChars(r.toString(16), 2, "0") + 
				addPrecedingChars(g.toString(16), 2, "0") +
				addPrecedingChars(b.toString(16), 2, "0");
			
			mOutputArea.text += e.message;
			// Update scroll position
			mOutputArea.verticalScrollPosition = mOutputArea.maxVerticalScrollPosition;
		}
		
		/**
		 * Adds additional characters at the begin of the given String. The number of characters
		 * depends on the length of the given String.
		 * @param str The String to add the specified characters to.
		 * @param length The total number of characters the String should be after adding characters.
		 * @param char The character to add.
		 * @return If the number of characters of the given String was less
		 * 	than the required length returns the converted String, otherwise the original String.
		 */
		private function addPrecedingChars(str:String, length:Number, char:String):String
		{
			var newStr:String = "";
			
			var l:Number = str.length;
			for (var i:Number = l; i < length; ++i)
			{
				newStr += char;
			}
			
			newStr += str;
			return newStr;
		}
	}
	
}